package dryven.request.controller;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import dryven.model.binding.proxy.Binding;
import dryven.reflection.DeclaringMethodIterator;
import dryven.request.controller.result.ActionResult;
import dryven.request.http.HttpUtils;
import dryven.request.routing.ActionParameterFormatException;
import dryven.util.TypeUtil;

public class ControllerDescription {
	private Class<?> controllerClass;
	private Method method;
	private Object[] parameters;
	
	public ControllerDescription(Binding<ActionResult> binding) {
		this(
				binding.getBoundMethod().getDeclaringClass(),
				binding.getBoundMethod(),
				binding.getBoundParams()
			);
	}
	
	public ControllerDescription(Class<?> clazz, Method method, Object ... params) {
		super();
		this.controllerClass = clazz;
		this.method = method;

		//initialize parameters array
		parameters = new Object[method.getParameterTypes().length];
		for(int i=0;i<parameters.length;++i) {
			parameters[i] = null;
		}
		//if we got some params passed, copy them
		if(params!=null) {
			if(params.length>parameters.length) {
				throw new IndexOutOfBoundsException(String.format("%s.%s expects a maximum of %d params, %d given", controllerClass.getName(), method.getName(), parameters.length, params.length));
			}
			for(int i=0;i<params.length;++i) {
				setActionParameter(i, params[i]);
			}
		}
	}
	
	public ControllerDescription(Class<?> clazz, String action, Object ... params) {
		this(clazz,convertMethodName(clazz, action, params.length),params);
	}
	
	public ControllerDescription(String className, String actionName, Object ... params) {
		this(convertClassName(className),actionName,params);
	}
	
	private static Method convertMethodName(Class<?> clazz, String name, int paramCount) {
		String altName = name+"_";
		for(Method m:new DeclaringMethodIterator(clazz)) {
			if(m.getName().equals(name) || m.getName().equals(altName)) {
				return m;
			}
		}
		throw new RuntimeException(String.format("Controller action %s not found in %s", name,clazz.getName()));
	}
	
	private static Class<?> convertClassName(String className) {
		Class<?> clazz;
		try {
			clazz = Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(String.format("Controller %s not found",className));
		}
		return clazz;
	}
	
	public void setActionParameter(int index, Object value) {
		if(index>=0 && index<parameters.length) {
			Class<?> expectedType = method.getParameterTypes()[index];
			//is the parameter of the right type?
			if(value!=null && !TypeUtil.typesAreAssignable(expectedType, value.getClass())) {
				throw new RuntimeException(String.format("%s.%s parameter %d expects a value of %s, %s given", controllerClass.getName(), method.getName(), index, expectedType.getName(), value.getClass().getName()));
			}
			parameters[index]=value;
		} else {
			throw new IndexOutOfBoundsException();
		}
	}
	
	public Object getActionParameter(int index) {
		if(index>=0 && index<parameters.length) {
			return parameters[index];
		} else {
			throw new IndexOutOfBoundsException();
		}
	}
	
	public Class<?> getControllerClass() {
		return controllerClass;
	}
	public Method getAction() {
		return method;
	}
	public Object[] getParameters() {
		return parameters;
	}
	
	public int getParameterCount() {
		return method.getParameterTypes().length;
	}
}